// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).

using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Serialization;

using Tierless.Framework.Data;
using Tierless.Framework.Entities;

namespace Tierless.Framework.EntityTypes
{
	/// <summary>
	/// Represents the possible types of columns.
	/// </summary>
	public enum EntityColumnType
	{
		/// <summary>
		/// The column represents information stored in the entity store.
		/// </summary>
		Stored,

		/// <summary>
		/// The column represents information computed in the entity, not stored.
		/// </summary>
		Computed
	}

	/// <summary>
	/// Represents the possible order directions for an order column definition.
	/// </summary>
	public enum OrderDirection
	{
		/// <summary>
		/// The values are sorted in ascending order.
		/// </summary>
		Ascending,

		/// <summary>
		/// The values are sorted in descending order.
		/// </summary>
		Descending
	}

	/// <summary>
	/// Represents the possible kinds of references.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Reference kind describes the type of reference with respect to a specific entity type.
	/// For example, suppose we have two entity types, Customer and Order, where an Order has 
	/// a Customer. From the perspective of the Order, the reference would be described as a 
	/// parent reference. However, from the perspective of the Customer, the reference to
	/// Order would be described as a child reference.
	/// </para>
	/// <para>
	/// In general, a lookup reference is a reference which 'points to' another entity type,
	/// where a child reference is a reference which 'originates in' another entity type.
	/// </para>
	/// <para>
	/// By design, this directedness is built in as an attribute of the entity reference. This means
	/// that to model the example relationship described above actually requires two references, one
	/// to describe the reference from the perspective of the Order, as a lookup reference, and one
	/// to describe the reference from the perspective of the Customer, as a child reference.
	/// </para>
	/// <para>
	/// Note that because a relationship in general must target a key, a specific instance of a lookup
	/// reference for an entity will result in a single referenced entity. By contrast, a child reference
	/// may either originate in a key, or a non-key. If the child reference originates in a key, there can
	/// be at most one child for any given entity. If the child reference originates in a non-key, there can be
	/// any number of children for any given entity. Regardless of the cardinality of children, child references
	/// are always modeled as collection properties of an entity.
	/// </para>
	/// </remarks>
	public enum ReferenceKind
	{
		/// <summary>
		/// The reference 'points to' another entity type.
		/// </summary>
		Lookup,

		/// <summary>
		/// The reference 'originates in' another entity type.
		/// </summary>
		Child
	}

	/// <summary>
	/// Represents the possible referential actions used to enforce a given reference.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Note that the cascade behavior is not actually implemented by Tierless, this is used to
	/// indicate that the underlying storage layer will provide cascade functionality.
	/// </para>
	/// <para>
	/// If actual cascade or clear functionality is required, the <c>Custom</c> referential action
	/// must be used.
	/// </para>
	/// </remarks>
	public enum ReferentialAction
	{
		/// <summary>
		/// The reference is enforced with a constraint violation.
		/// </summary>
		Require,

		/// <summary>
		/// The reference is maintained such that a change in the target is cascaded to the source.
		/// </summary>
		Cascade,

		/// <summary>
		/// The reference is maintained such that a change in the target is cleared at the source.
		/// </summary>
		Clear,

		/// <summary>
		/// The reference is maintained via custom logic provided as an extension.
		/// </summary>
		Custom
	}

	/// <summary>
	/// Represents an entity type in the model.
	/// </summary>
	[DataContract]
	public class EntityType : NamedEntityTypeObject
	{
		/// <summary>
		/// Initializes a new instance of the EntityType class.
		/// </summary>
		/// <param name="name">The name of the entity type.</param>
		public EntityType(string name)
			: base(name)
		{
			Title = name;
			PluralTitle = name;
			_keys = new EntityKeyCollection();
			_keys.CollectionChanged += KeysCollectionChanged;
		}

		/// <summary>
		/// Gets or sets the title of the entity type.
		/// </summary>
		[DataMember]
		public string Title { get; set; }

		/// <summary>
		/// Gets or sets the title of the entity type to be used in plural contexts.
		/// </summary>
		[DataMember]
		public string PluralTitle { get; set; }

		[DataMember]
		private EntityTypeColumnCollection _columnTypes = new EntityTypeColumnCollection();

		/// <summary>
		/// Gets the collection of column descriptions for the entity type.
		/// </summary>
		public EntityTypeColumnCollection ColumnTypes
		{
			get { return _columnTypes; }
		}

		[DataMember]
		private EntityKeyCollection _keys;

		/// <summary>
		/// Gets the collection of entity keys for the entity type.
		/// </summary>
		public EntityKeyCollection Keys
		{
			get { return _keys; }
		}

		private bool _primaryKeyCached;

		private EntityKey _primaryKey;

		/// <summary>
		/// Gets the primary key for the entity type.
		/// </summary>
		public EntityKey PrimaryKey
		{
			get
			{
				if (!_primaryKeyCached)
				{
					_primaryKey = _keys.FirstOrDefault(k => k.IsPrimary);
					_primaryKeyCached = true;
				}
				return _primaryKey;
			}
		}

		private void KeysCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			_primaryKeyCached = false;
			_primaryKey = null;
		}

		/// <summary>
		/// Gets or sets whether or not the entity type is temporal.
		/// </summary>
		[DataMember]
		public virtual bool IsTemporal { get; set; }

		/// <summary>
		/// Gets or sets the name of the from date column.
		/// </summary>
		[DataMember]
		public string FromDateColumnName { get; set; }

		private int? _fromDateColumnIndex;

		/// <summary>
		/// Gets the index of the from date column.
		/// </summary>
		public int FromDateColumnIndex
		{
			get
			{
				if (!_fromDateColumnIndex.HasValue)
					_fromDateColumnIndex = _columnTypes.IndexOf(FromDateColumnName);
				return _fromDateColumnIndex.Value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the to date column.
		/// </summary>
		[DataMember]
		public string ToDateColumnName { get; set; }

		private int? _toDateColumnIndex;

		/// <summary>
		/// Gets the index of the to date column.
		/// </summary>
		public int ToDateColumnIndex
		{
			get
			{
				if (!_toDateColumnIndex.HasValue)
					_toDateColumnIndex = _columnTypes.IndexOf(ToDateColumnName);
				return _toDateColumnIndex.Value;
			}
		}

		/// <summary>
		/// Gets or sets the default traversal to be used for entity requests for this entity type.
		/// </summary>
		[DataMember]
		public EntityTraversal DefaultTraversal { get; set; }

		[DataMember]
		private EntityOrderCollection _orders = new EntityOrderCollection();

		/// <summary>
		/// Gets the collection of orders defined for this entity type.
		/// </summary>
		public EntityOrderCollection Orders
		{
			get { return _orders; }
		}

		[DataMember]
		private EntityOrder _defaultOrder;

		/// <summary>
		/// Gets or sets the default order for this entity type.
		/// </summary>
		public EntityOrder DefaultOrder
		{
			get { return _defaultOrder; }
			set { _defaultOrder = value; }
		}

		[DataMember]
		private EntityConstraintCollection _constraints = new EntityConstraintCollection();

		/// <summary>
		/// Gets or sets the collection of constraints defined for this entity type.
		/// </summary>
		public EntityConstraintCollection Constraints
		{
			get { return _constraints; }
		}

		[DataMember]
		private EntityReferenceCollection _references = new EntityReferenceCollection();

		/// <summary>
		/// Gets the set of references defined for this entity type.
		/// </summary>
		public EntityReferenceCollection References
		{
			get { return _references; }
		}

		/// <summary>
		/// Returns true if the given entity meets the criteria of relevance for this entity type.
		/// </summary>
		/// <param name="entity">The entity to be tested.</param>
		/// <returns>True if the entity meets the criteria of relevance for this entity type.</returns>
		public virtual bool IsRelevant(Entity entity)
		{
			return true;
		}

		protected internal virtual object[] DefaultData(Entity entity)
		{
			var result = new object[ColumnTypes.Count];
			for (var index = 0; index < ColumnTypes.Count; index++)
				result[index] = ColumnTypes[index].DefaultData(entity.EntityType);
			return result;
		}

		/// <summary>
		/// Provides initialization logic for this entity type.
		/// </summary>
		/// <remarks>
		/// Override this method to provide additional structural information about the entity type.
		/// This method is called after all automatic inference is complete, and before the 
		/// FinishInitialization call on the server entity type.
		/// </remarks>
		public virtual void FinishInitialization()
		{}

		/// <summary>
		/// Returns a string representation of the entity type.
		/// </summary>
		/// <returns>A string representation of the entity type.</returns>
		public override string ToString()
		{
			return string.Format("EntityType: Name={0};", Name);
		}
	}
}